#!/usr/bin/env python3
# --------------------( LICENSE                            )--------------------
# Copyright (c) 2014-2025 Beartype authors.
# See "LICENSE" for further details.

'''
Beartype decorator :pep:`492`-compliant asynchronous coroutine unit tests.

This submodule unit tests :pep:`492` support for asynchronous generators
implemented in the :func:`beartype.beartype` decorator.
'''

# ....................{ IMPORTS                            }....................
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# WARNING: To raise human-readable test errors, avoid importing from
# package-specific submodules at module scope.
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
from beartype.roar import BeartypeDecorHintPep585DeprecationWarning
from beartype_test._util.mark.pytmark import ignore_warnings

# ....................{ TESTS ~ async                      }....................
# Prevent pytest from capturing and displaying all expected non-fatal
# beartype-specific warnings emitted by the @beartype decorator below. Urgh!
@ignore_warnings(BeartypeDecorHintPep585DeprecationWarning)
async def test_decor_pep492_async_coroutine() -> None:
    '''
    Test decorating **coroutines** (i.e., asynchronous non-generator functions)
    with the :func:`beartype.beartype` decorator.
    '''

    # ....................{ IMPORTS                        }....................
    # Defer test-specific imports.
    from asyncio import sleep
    from beartype import beartype
    from beartype.roar import (
        BeartypeCallHintParamViolation,
        BeartypeDecorHintPep585Exception,
    )
    from beartype_test._util.pytroar import raises_uncached
    from collections.abc import Coroutine as Pep585Coroutine
    from inspect import (
        isasyncgenfunction,
        iscoroutinefunction,
    )
    from typing import (
        Union,
        Coroutine as Pep484Coroutine,
    )

    # ....................{ LOCALS                         }....................
    # Tuple of all PEP 484- or 585-compliant type hints annotating the returns
    # of synchronous generators covering all possible edge cases.
    HINTS_RETURN = (
        # ....................{ PEP 484                    }....................
        Pep484Coroutine[None, None, Union[str, float]],
        Union[str, float],

        # ....................{ PEP 585                    }....................
        Pep585Coroutine[None, None, Union[str, float]],
    )

    # ....................{ PASS                           }....................
    # For each return type hint defined above...
    for hint_return in HINTS_RETURN:
        @beartype
        async def control_the_car(
            said_the: Union[str, int], biggest_greenest_bat: Union[str, float],
        ) -> hint_return:
            '''
            :func:`beartype.beartype`-decorated coroutine whose return is
            annotated by a :pep:`484`- or :pep:`585`-compliant type hint deeply
            type-checking the value returned by this coroutine.
            '''

            # Silently reduce to an asynchronous noop. *ALL* coroutines are
            # required to call the "await" keyword at least once. Since the
            # placeholder object returned below is synchronous and thus *CANNOT*
            # be asynchronously awaited, this fallback wrapper has *NO* recourse
            # but to asynchronously await a minimal-cost awaitable. Aaaaaaand...
            #
            # This is why the "asyncio" API is Python's most hated. We sigh!
            await sleep(0)

            # Return an arbitrary combination of the passed parameters.
            return said_the + biggest_greenest_bat

        # Assert that the high-level wrapper generated by the @beartype
        # decorator type-checking the low-level coroutine defined above
        # preserves "inspect"-based metadata describing this coroutine.
        # Specifically, assert that this wrapper is still a coroutine but has
        # *NOT* been improperly transformed into an asynchronous generator.
        assert iscoroutinefunction(control_the_car) is True
        assert isasyncgenfunction(control_the_car) is False

        # Assert that awaiting the coroutine created by passing this function
        # valid parameters returns the expected value.
        assert await control_the_car(
            'I saw the big green bat bat a green big eye. ',
            'Suddenly I knew I had gone too far.') == (
            'I saw the big green bat bat a green big eye. '
            'Suddenly I knew I had gone too far.')

        # Assert that awaiting the coroutine created by passing this function
        # invalid parameters raises the expected violation.
        with raises_uncached(BeartypeCallHintParamViolation):
            await control_the_car(
                b'The sea was made of strontium;',
                b'the beach was made of rye.',
            )

    # ....................{ FAIL                           }....................
    # Assert that @beartype raises the expected exception when decorating a
    # coroutine whose return is annotated by a PEP 585-compliant coroutine type
    # hint *NOT* subscripted by exactly three child hints.
    with raises_uncached(BeartypeDecorHintPep585Exception):
        @beartype
        async def with_each_planck_moment_ever_fit() -> (
            Pep585Coroutine['to be eternally enjoyed']):
            # Silently reduce to an asynchronous noop. See above for details.
            await sleep(0)

            # Return an arbitrary object (sorta) satisfying this return hint.
            return 'Time will decay us but time can be left blank'
